//
//  CHHub3Device.swift
//  SesameSDK
//
//  Created by eddy on 2023/12/26.
//  Copyright © 2023 CandyHouse. All rights reserved.
//

import Foundation
import CoreBluetooth

class CHHub3Device: CHSesameOS3, CHHub3, CHDeviceUtil {
    
    var sesame2Keys = [String: String]() {
        didSet {
            (self.delegate as? CHWifiModule2Delegate)?.onSesame2KeysChanged(device: self, sesame2keys: sesame2Keys)
        }
    }
    
    var mechSetting: CHWifiModule2MechSettings? = CHWifiModule2MechSettings()
    
    var advertisement: BleAdv? {
        didSet {
            guard let advertisement = advertisement else {
                deviceStatus = .noBleSignal()
                return
            }
            setAdv(advertisement)
        }
    }
    
    override func onGattSesamePublish(_ payload: SesameOS3PublishPayload) {
        super.onGattSesamePublish(payload)
        onGattHub3Publish(payload)
    }
    
    func goIOT() {
#if os(iOS)
        CHIoTManager.shared.subscribeWifiModule2Shadow(self) { result in
            switch result {
            case .success(let result):
                self.wifiModule2ShadowCompleteHandler(result: result.data)
            case .failure(let error):
                L.d(error)
            }
        }
#endif
    }
    
    private func wifiModule2ShadowCompleteHandler(result: WifiModuleShadow) {
        let networkStatus = (self.mechStatus as? CHWifiModule2NetworkStatus)
        self.mechStatus = CHWifiModule2NetworkStatus(isAPWork: networkStatus?.isAPWork,
                                                        isNetwork: networkStatus?.isNetwork,
                                                        isIoTWork: result.isConnected,
                                                        isBindingAPWork: false,
                                                        isConnectingNetwork: false,
                                                        isConnectingIoT: false)
        
        if CHBluetoothCenter.shared.scanning == .bleClose() ||
            self.deviceStatus.loginStatus == .unlogined {
            self.sesame2Keys = result.sesame2Keys
            (self.delegate as? CHWifiModule2Delegate)?.onSesame2KeysChanged(device: self, sesame2keys: self.sesame2Keys)
        }
    }
    
    deinit {
#if os(iOS)
        CHIoTManager.shared.unsubscribeWifiModule2Shadow(self)
#endif
    }
}

// register
extension CHHub3Device {
    
    /// 登录
    /// - Parameter token: 用戶token
    func login(token: String? = nil) {
        guard let sesame2KeyData = sesame2KeyData, let sessionToken = mSesameToken else { return }
        self.deviceStatus = .bleLogining()
        let sessionAuth: Data = token?.hexStringtoData() ?? CC.CMAC.AESCMAC(sessionToken, key: sesame2KeyData.secretKey.hexStringtoData())
        self.cipher = SesameOS3BleCipher(name: self.deviceId.uuidString,sessionKey: sessionAuth,sessionToken:("00"+sessionToken.toHexString()).hexStringtoData())
        self.commandQueue = DispatchQueue(label: deviceId.uuidString, qos: .userInitiated)
        sendCommand(.init(.login, sessionAuth[0...3]), isCipher: .plaintext) { [weak self] res in
            guard let self = self else { return }
            self.deviceStatus = .unlocked()
        }
    }
    
    public func register(result: @escaping CHResult<CHEmpty>)  {
        if deviceStatus != .readyToRegister() {
            result(.failure(NSError.deviceStatusNotReadyToRegister))
            return
        }
        deviceStatus = .registering()

        let date = Date()
        var timestamp: UInt32 = UInt32(date.timeIntervalSince1970)
        let timestampData = Data(bytes: &timestamp,count: MemoryLayout.size(ofValue: timestamp))
        let payload = Data(appKeyPair.publicKey)+timestampData
        self.commandQueue = DispatchQueue(label:deviceId.uuidString, qos: .userInitiated)

        let request = CHAPICallObject(.post, "/device/v1/sesame5/\(self.deviceId.uuidString)", [
            "t":advertisement!.productType!.rawValue,
            "pk":self.mSesameToken!.toHexString()
        ] as [String : Any])
//        L.d("[ss5][register] ==>")
        CHAccountManager
            .shared
            .API(request: request) { response in
                switch response {
                case .success(_):
//                    L.d("[ss5][register][ok <==]")
//                    L.d("[ss5][register][ble] ==>]")
                    self.sendCommand(.init(.registration, payload), isCipher: .plaintext) { response in
//                        L.d("[ss5][register][ble] <==]")
                        let ecdhSecretPre16 = Data(self.appKeyPair.ecdh(remotePublicKey: response.data[0...63].bytes))[0...15]
                        let sessionAuth = CC.CMAC.AESCMAC(self.mSesameToken!, key: ecdhSecretPre16)

                        self.cipher = SesameOS3BleCipher(name: self.deviceId.uuidString,
                                             sessionKey: sessionAuth,
                                                           sessionToken: ("00\(self.mSesameToken!.toHexString())").hexStringtoData())

                        self.sesame2KeyData = CHDeviceKey(// 建立設備
                            deviceUUID: self.deviceId,
                            deviceModel: self.productModel.deviceModel(),
                            historyTag: nil,
                            keyIndex: "0000",
                            secretKey: ecdhSecretPre16.toHexString(),
                            sesame2PublicKey: self.mSesameToken!.toHexString()
                        )
                        self.isRegistered = true // 設定為已註冊
                        self.goIOT()
                        CHDeviceCenter.shared.appendDevice(self.sesame2KeyData!) // 存到SDK層的DB中
                        self.deviceStatus = .unlocked()
                        result(.success(CHResultStateNetworks(input: CHEmpty())))
                    }
                case .failure(let error):
                    L.d("[ss5]register error",error)
                    result(.failure(error))
                    self.disconnect(){_ in}
                }
            }
    }
    
    /// 更新固件
    /// - Parameter result: 返回 peripheral
    func updateFirmware(result: @escaping CHResult<CBPeripheral?>) {
        if (self.checkBle(result)) { return }
        L.d("Hub3 开始 ota 升级")
        sendCommand(.init(SesameItemCode.moveTo)) { response in
            result(.success(.init(input: nil)))
        }
    }
}

// Infrared 紅外處理
extension CHHub3Device {
    
    func scanWifiSSID(result: @escaping CHResult<CHEmpty>) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][scanWifiSSID]")
        sendCommand(.init(SesameItemCode.HUB3_ITEM_CODE_WIFI_SSID)) { payload in
            if payload.cmdResultCode == .success {
                result(.success(CHResultStateBLE(input:CHEmpty())))
            } else {
                result(.failure(self.errorFromResultCode(payload.cmdResultCode)))
            }
        }
    }
    
    func connectWifi(result: @escaping (CHResult<CHEmpty>)) {
        result(.success(CHResultStateBLE(input:CHEmpty())))
    }
    
    func setWifiSSID(_ ssid: String, result: @escaping (CHResult<CHEmpty>)) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][setWifiSSID]", ssid)
        sendCommand(.init(SesameItemCode.HUB3_UPDATE_WIFI_SSID, ssid.data(using: .utf8))) { payload in
            if payload.cmdResultCode == .success {
                result(.success(CHResultStateBLE(input:CHEmpty())))
            } else {
                result(.failure(self.errorFromResultCode(payload.cmdResultCode)))
            }
        }
    }
    
    func setWifiPassword(_ password: String, result: @escaping (CHResult<CHEmpty>)) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][setWifiPassword]", password)
        sendCommand(.init(SesameItemCode.HUB3_ITEM_CODE_WIFI_PASSWORD, password.data(using: .utf8))) { payload in
            if payload.cmdResultCode == .success {
                result(.success(CHResultStateBLE(input:CHEmpty())))
            } else {
                result(.failure(self.errorFromResultCode(payload.cmdResultCode)))
            }
        }
    }
    
    func insertSesame(_ device: CHDevice, result: @escaping CHResult<CHEmpty>) {}
    
    func insertSesame(_ device: CHDevice, matterProductModel: MatterProductModel, result: @escaping CHResult<CHEmpty>) {
        if (self.checkBle(result)) { return }
        let noDashUUID = device.deviceId.uuidString.replacingOccurrences(of: "-", with: "", options: [], range: nil)
        let noDashUUIDData = noDashUUID.hexStringtoData()
        let ssmSecKa = device.getKey()!.secretKey.hexStringtoData()
        let matter = UInt8(device.productModel.rawValue).data + matterProductModel.rawValue.data
        sendCommand(.init(.addSesame,noDashUUIDData + ssmSecKa + matter)) { (response) in
            result(.success(CHResultStateNetworks(input: CHEmpty())))
        }
    }
    
    func removeSesame(tag: String, result: @escaping CHResult<CHEmpty>) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][removeSesame]",tag)
        let noDashUUID = tag.replacingOccurrences(of: "-", with: "", options: [], range: nil)
        sendCommand(.init(.removeSesame,noDashUUID.hexStringtoData())) { (response) in
            result(.success(CHResultStateNetworks(input: CHEmpty())))
        }
    }
    
    func irModeGet(result: @escaping CHResult<UInt8>) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][irModeGet]")
        sendCommand(.init(SesameItemCode.SSM_OS3_IR_MODE_GET)) { payload in
            if payload.cmdResultCode == .success {
                result(.success(CHResultStateBLE(input:payload.data[0])))
            } else {
                result(.failure(self.errorFromResultCode(payload.cmdResultCode)))
            }
        }
    }
    
    func irModeSet(mode: UInt8, result: @escaping CHResult<CHEmpty>) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][irModeSet]",mode)
        sendCommand(.init(SesameItemCode.SSM_OS3_IR_MODE_SET, mode.data)) { payload in
            if payload.cmdResultCode == .success {
                result(.success(CHResultStateBLE(input:CHEmpty())))
            } else {
                result(.failure(self.errorFromResultCode(payload.cmdResultCode)))
            }
        }
    }
    
    func irCodeEmit(id: UInt8, result: @escaping CHResult<CHEmpty>) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][irCodeEmit]",id)
        sendCommand(.init(SesameItemCode.SSM_OS3_IR_CODE_EMIT, Data([id]))) { payload in
            if payload.cmdResultCode == .success {
                result(.success(CHResultStateBLE(input:CHEmpty())))
            } else {
                result(.failure(self.errorFromResultCode(payload.cmdResultCode)))
            }
        }
    }
    
    func irCodeDelete(id: UInt8, result: @escaping CHResult<CHEmpty>) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][irCodeDelete]",id)
        sendCommand(.init(SesameItemCode.SSM_OS3_IR_CODE_DELETE, Data([id]))) { payload in
            if payload.cmdResultCode == .success {
                result(.success(CHResultStateBLE(input:CHEmpty())))
            } else {
                result(.failure(self.errorFromResultCode(payload.cmdResultCode)))
            }
        }
    }
    
    func irCodesGet(result: @escaping CHResult<CHEmpty>) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][irCodesGet]")
        sendCommand(.init(SesameItemCode.SSM_OS3_IR_CODE_GET)) { payload in
            if payload.cmdResultCode == .success {
                result(.success(CHResultStateBLE(input:CHEmpty())))
            } else {
                result(.failure(self.errorFromResultCode(payload.cmdResultCode)))
            }
        }
    }
    
    func irCodeChange(id: UInt8, name: [UInt8], result: @escaping CHResult<CHEmpty>) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][irCodeChange]", id, name)
        sendCommand(.init(SesameItemCode.SSM_OS3_IR_CODE_CHANGE, Data([id]) + Data(name))) { payload in
            if payload.cmdResultCode == .success {
                result(.success(CHResultStateBLE(input:CHEmpty())))
            } else {
                result(.failure(self.errorFromResultCode(payload.cmdResultCode)))
            }
        }
    }
    
    func getMatterParingCode(result: @escaping CHResult<CHHub3MatterSettings>) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][getMatterParingCode]")
        sendCommand(.init(SesameItemCode.HUB3_MATTER_PAIRING_CODE)) { payload in
            if payload.cmdResultCode == .success, let resp = CHHub3MatterSettings.fromData(payload.data) {
                result(.success(CHResultStateBLE(input: resp)))
            } else {
                result(.failure(self.errorFromResultCode(payload.cmdResultCode)))
            }
        }
    }
    
    func openMatterPairingWindow(result: @escaping CHResult<CHEmpty>) {
        if (self.checkBle(result)) { return }
        L.d("[hub3][openMatterPairingWindow]")
        sendCommand(.init(SesameItemCode.HUB3_MATTER_PAIRING_WINDOW)) { payload in
            if payload.cmdResultCode == .success {
                result(.success(CHResultStateBLE(input:CHEmpty())))
            } else {
                result(.failure(self.errorFromResultCode(payload.cmdResultCode)))
            }
        }
    }
}
